﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;

namespace Dynamic
{
    public class ValueAccessor
    {
        private ValueAccessor(Func<object,object> getter, Action<object,object> setter)
        {
            Getter = getter;
            Setter = setter;
        }
        public object Get(object obj)
        {
            return Getter(obj);
        }
        public void Set(object obj,object value)
        {
            Setter(obj,value);
        }
        public Func<object,object> Getter { get; private set; }
        public Action<object,object> Setter { get; private set; }
        public static ValueAccessor CreateAccessor(FieldInfo field)
        {
            var tobj = typeof(object);
            var InstanceParam = Expression.Parameter(tobj);
            var valueParam = Expression.Parameter(tobj);
            var obj=field.DeclaringType.IsValueType?Expression.Unbox(InstanceParam,field.DeclaringType):Expression.TypeAs(InstanceParam,field.DeclaringType);
            var value = field.FieldType.IsValueType ? Expression.Unbox(valueParam, field.FieldType) : Expression.TypeAs(valueParam, field.FieldType);
            return new ValueAccessor(
                Expression.Lambda<Func<object,object>>(Expression.TypeAs(Expression.Field(obj, field), typeof(object)), InstanceParam).Compile(),
                Expression.Lambda<Action<object,object>>(Expression.Assign(Expression.Field(obj, field),value), InstanceParam, valueParam).Compile()
                );
        }
        public static ValueAccessor CreateAccessor(PropertyInfo prop)
        {
            var tobj = typeof(object);
            var InstanceParam = Expression.Parameter(tobj);
            var valueParam = Expression.Parameter(tobj);
            var obj = prop.DeclaringType.IsValueType ? Expression.Unbox(InstanceParam, prop.DeclaringType) : Expression.TypeAs(InstanceParam, prop.DeclaringType);
            var value = prop.PropertyType.IsValueType ? Expression.Unbox(valueParam, prop.PropertyType) : Expression.TypeAs(valueParam, prop.PropertyType);
            return new ValueAccessor(
                Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Property(obj, prop),tobj), InstanceParam).Compile(),
                Expression.Lambda<Action<object, object>>(Expression.Assign(Expression.Property(obj, prop), value), InstanceParam, valueParam).Compile()
                );
        }
    }
    public class ValueAccessor<TMember>
    {
        private ValueAccessor(Func<object, TMember> getter, Action<object, TMember> setter)
        {
            Getter = getter;
            Setter = setter;
        }
        public TMember Get(object obj)
        {
            return Getter(obj);
        }
        public void Set(object obj, TMember value)
        {
            Setter(obj, value);
        }
        public Func<object, TMember> Getter { get; private set; }
        public Action<object, TMember> Setter { get; private set; }
        public static ValueAccessor<TMember> CreateAccessor(FieldInfo field)
        {
            var InstanceParam = Expression.Parameter(typeof(object));
            var valueParam = Expression.Parameter(typeof(TMember));
            var obj = field.DeclaringType.IsValueType ? Expression.Unbox(InstanceParam, field.DeclaringType) : Expression.TypeAs(InstanceParam, field.DeclaringType);
            return new ValueAccessor<TMember>(
                Expression.Lambda<Func<object, TMember>>(Expression.Field(obj, field), InstanceParam).Compile(),
                Expression.Lambda<Action<object, TMember>>(Expression.Assign(Expression.Field(obj, field), valueParam), InstanceParam, valueParam).Compile());
        }
        public static ValueAccessor<TProperty> CreateAccessor<TProperty>(PropertyInfo prop)
        {
            var InstanceParam = Expression.Parameter(typeof(object));
            var valueParam = Expression.Parameter(typeof(TProperty));
            var obj = prop.DeclaringType.IsValueType ? Expression.Unbox(InstanceParam, prop.DeclaringType) : Expression.TypeAs(InstanceParam, prop.DeclaringType);
            var value = prop.PropertyType.IsValueType ? Expression.Unbox(valueParam, prop.PropertyType) : Expression.TypeAs(valueParam, prop.PropertyType);
            return new ValueAccessor<TProperty>(
                Expression.Lambda<Func<object, TProperty>>(Expression.Property(obj, prop), InstanceParam).Compile(),
                Expression.Lambda<Action<object, TProperty>>(Expression.Assign(Expression.Property(obj, prop), value), InstanceParam, valueParam).Compile());
        }
    }
    public class ValueAccessor<TInstance,TMember>
    {
        private ValueAccessor(Func<TInstance, TMember> getter, Action<TInstance, TMember> setter)
        {
            Getter = getter;
            Setter = setter;
        }
        public TMember Get(TInstance obj)
        {
            return Getter(obj);
        }
        public void Set(TInstance obj, TMember value)
        {
            Setter(obj, value);
        }
        public Func<TInstance, TMember> Getter { get; private set; }
        public Action<TInstance, TMember> Setter { get; private set; }
        public static ValueAccessor<TInstance,TMember> CreateAccessor(FieldInfo field)
        {
            var InstanceParam = Expression.Parameter(typeof(TInstance));
            var valueParam = Expression.Parameter(typeof(TMember));
            //var obj = field.DeclaringType.IsValueType ? Expression.Unbox(InstanceParam, field.DeclaringType) : Expression.TypeAs(InstanceParam, field.DeclaringType);
            //var value = field.FieldType.IsValueType ? Expression.Unbox(valueParam, field.FieldType) : Expression.TypeAs(valueParam, field.FieldType);
            return new ValueAccessor<TInstance,TMember>(
                Expression.Lambda<Func<TInstance, TMember>>(Expression.Field(InstanceParam, field), InstanceParam).Compile(),
                Expression.Lambda<Action<TInstance, TMember>>(Expression.Assign(Expression.Field(InstanceParam, field), valueParam), InstanceParam, valueParam).Compile());
        }
        public static ValueAccessor<TInstance,TProperty> CreateAccessor<TProperty>(PropertyInfo prop)
        {
            var InstanceParam = Expression.Parameter(typeof(TInstance));
            var valueParam = Expression.Parameter(typeof(TProperty));
            //var obj = prop.DeclaringType.IsValueType ? Expression.Unbox(InstanceParam, prop.DeclaringType) : Expression.TypeAs(InstanceParam, prop.DeclaringType);
            //var value = prop.PropertyType.IsValueType ? Expression.Unbox(valueParam, prop.PropertyType) : Expression.TypeAs(valueParam, prop.PropertyType);
            return new ValueAccessor<TInstance,TProperty>(
                Expression.Lambda<Func<TInstance, TProperty>>(Expression.Property(InstanceParam, prop), InstanceParam).Compile(),
                Expression.Lambda<Action<TInstance, TProperty>>(Expression.Assign(Expression.Property(InstanceParam, prop), valueParam), InstanceParam, valueParam).Compile());
        }
    }
}
